/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package shaman.storage.impl.stream.filter.block;

import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;

/**
 *
 * @author dmitriy
 */
public class BlockFilterInputStream extends InputStream {

    private final BlockTransformer blkTrans;
    private final int blkSize;
    private final byte[] buffer;
    private final InputStream in;

    private int bufferPos;

    public BlockFilterInputStream(InputStream baseStream, BlockTransformer blkTrans) {
        in=baseStream;
        this.blkTrans = blkTrans;
        blkSize=this.blkTrans.getBlockSize();
        buffer=new byte[blkSize];
    }

    @Override
    public int available() throws IOException {
        return super.available();
    }

    @Override
    public synchronized void mark(int readlimit) {
        super.mark(readlimit);
    }

    @Override
    public boolean markSupported() {
        return false;
    }

    @Override
    public int read() throws IOException {
        if (bufferPos==0)
            if (!readIn()) return -1;
        int ret=buffer[bufferPos++];
        if (bufferPos>=blkSize)
            bufferPos=0;
        if (ret<0)
            ret+=256;
        return ret;

    }

    @Override
    public int read(byte[] b, int off, int len) throws IOException {
        int readLen=0;
        while (readLen<len)
        {
            int partLen=doRead(b, off+readLen, len-readLen);
            if (partLen<0)
                break;
            readLen+=partLen;
        }
        if (readLen==0)
            readLen=-1;
        return readLen;
    }

    @Override
    public synchronized void reset() throws IOException {
        throw new IOException("Mark not supported");
    }

    @Override
    public long skip(long n) throws IOException {
        long skippedLen=0;
        while (skippedLen<n)
        {
            long partLen=doSkip(n-skippedLen);
            if (partLen<0)
                break;
            skippedLen+=partLen;
        }
        if (skippedLen==0)
            skippedLen=-1;
        return skippedLen;
    }

    private int doRead(byte[] b, int off, int len) throws IOException {
        if (bufferPos==0)
            if (!readIn()) return -1;
        int availLen=(blkSize-bufferPos%blkSize);
        int realLen=Math.min(len, availLen);
        System.arraycopy(buffer, bufferPos, b, off, realLen);
        bufferPos+=realLen;
        if (bufferPos>=buffer.length)
            bufferPos=0;
        return realLen;
    }

    private long doSkip(long len) throws IOException {
        if (bufferPos>0)
        {
            int availLen=(blkSize-bufferPos%blkSize);
            long realLen=Math.min(len, availLen);
            bufferPos+=realLen;
            if (bufferPos>=buffer.length)
                bufferPos=0;
            return realLen;
        }
        else {
            if (len<blkSize)
            {
                if (!readIn()) return -1;
                bufferPos=(int) len;
                return len;
            }
            else {
                long skipLen=len-len%blkSize;
                long skippedLen=0;
                while (skippedLen<skipLen)
                {
                    long stepLen=in.skip(len);
                    if (stepLen<0)
                        break;
                    skippedLen+=stepLen;
                }
                if (skippedLen%blkSize!=0)
                    throw new EOFException("Incomplete block skipped");
                if (skippedLen==0)
                    skippedLen=-1;
                return skippedLen;
            }
        }
    }

    private boolean readIn() throws IOException {
        int readLen=0;
        while (readLen<blkSize)
        {
            int retLen=in.read(buffer, readLen, blkSize-readLen);
            if (retLen<0)
            {
                if (readLen>0)
                    throw new EOFException("Incomplete block read");
                else
                    return false;
            }
            readLen+=retLen;
        }
        blkTrans.reverse(buffer);
        return true;
    }

    @Override
    public void close() throws IOException {
        super.close();
    }
}
